package com.mxx.common.conv;

import com.mxx.common.exception.UnImplementedException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cglib.proxy.*;
import org.springframework.util.Assert;

import java.lang.reflect.Method;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author: mxx
 * @date: 2021/10/12
 * @time: 16:02
 * @Description: 描述信息
 */
@Slf4j
public class ProxyFactory {

    private static Map<Class, Object> instanceMapping = new ConcurrentHashMap<>();

    public static <T> T getMapper(Class<T> classes) {
        return getMapper(classes, null);
    }

    public static <T> T refreshMapper(Class<T> classes, T example) {
        Assert.notNull(example, "subExample must be null");
        Assert.notNull(classes, "classes must be null");
        Assert.state(classes.isInterface(), classes.getName() + " is not interface");
        Object instance = instanceMapping.get(classes);
        Factory factory = (Factory) instance;
        CglibCallback callback = (CglibCallback) factory.getCallback(0);
        callback.example = example;
        instanceMapping.put(classes, instance);
        return (T) instance;
    }


    public static <T> T getMapper(Class<T> classes, Object example) {
        Assert.notNull(classes, "classes must be null");
        Assert.state(classes.isInterface(), classes.getName() + " is not interface");

        Object instance = instanceMapping.get(classes);
        if (instance != null) {
            return (T) instance;
        }
        synchronized (classes) {
            instance = instanceMapping.get(classes);
            if (instance == null) {
                if (example == null) {
                    example = Proxy.newProxyInstance(classes.getClassLoader(), new Class[]{classes}, new UnImplementedHandler());
                }
                Enhancer enhancer = new Enhancer();
                enhancer.setSuperclass(classes);
                enhancer.setCallback(new CglibCallback(example));
                instance = enhancer.create();
                instanceMapping.put(classes, instance);
            }
            return (T) instance;
        }
    }


    private static class CglibCallback implements MethodInterceptor {
        private Object example;

        public CglibCallback(Object example) {
            this.example = example;
        }

        @Override
        public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
            Class declaringClass = method.getDeclaringClass();
            if (declaringClass.equals(Object.class)) {
                return method.invoke(this, objects);
            }
            return method.invoke(example, objects);
        }
    }


    private static class UnImplementedHandler implements InvocationHandler {
        @Override
        public Object invoke(Object proxy, Method method, Object[] objects) throws Throwable {
            throw new UnImplementedException(method.getDeclaringClass().getName() + " unImplement");
        }
    }

}
